require "timers"
require "libraries.minHeap"
require "constant"
-- local block = require "block"
local M = {}
local diceSize = 50
local sec_round = 60
local toRemove = {}
local session = 1
local function cmp( a,b )
	return a.expire < b.expire
end
local custom_timer = minHeap:newHeap(cmp)
local function doCreateDice( pos )
	unit = CreateUnitByName("npc_dice", pos, true, nil, nil, DOTA_TEAM_NEUTRALS)
	unit:SetAbsOrigin(pos)
	unit:SetModelScale(0.8)
	unit.origin_pos = pos
	unit:AddNoDraw()
	return unit
end

function M.init(  )
	GameRules.last_round_timer = -1  --初始化
	GameRules.center_unit = CreateUnitByName("npc_center",Vector(0,0,0),true,nil,nil,DOTA_TEAM_NEUTRALS)
	GameRules.dices = {} --色子单位
	--LinkLuaModifier( "modifier_dice_motion" , "scripts/vscripts/modifiers/modifier_dice_motion.lua" , LUA_MODIFIER_MOTION_BOTH )
	--这种带有motionControl的modifier有BUG，无法使用Apply函数
	LinkLuaModifier( "modifier_command_restricted" , "scripts/vscripts/modifiers/modifier_command_restricted.lua" , LUA_MODIFIER_MOTION_NONE )
	LinkLuaModifier( "modifier_ball_lightning" , "scripts/vscripts/modifiers/modifier_ball_lightning.lua" , LUA_MODIFIER_MOTION_NONE )
	LinkLuaModifier( "modifier_bainiu_motion" , "scripts/vscripts/modifiers/modifier_bainiu_motion.lua" , LUA_MODIFIER_MOTION_NONE )
	LinkLuaModifier( "modifier_kunkka_motion" , "scripts/vscripts/modifiers/modifier_kunkka_motion.lua" , LUA_MODIFIER_MOTION_NONE )
	LinkLuaModifier( "modifier_morphling_motion" , "scripts/vscripts/modifiers/modifier_morphling_motion.lua" , LUA_MODIFIER_MOTION_NONE )
	LinkLuaModifier( "modifier_magnataur_motion" , "scripts/vscripts/modifiers/modifier_magnataur_motion.lua" , LUA_MODIFIER_MOTION_NONE )
	LinkLuaModifier( "modifier_rubick_motion" , "scripts/vscripts/modifiers/modifier_rubick_motion.lua" , LUA_MODIFIER_MOTION_NONE )
	LinkLuaModifier( "modifier_ruyu_motion" , "scripts/vscripts/modifiers/modifier_ruyu_motion.lua" , LUA_MODIFIER_MOTION_NONE )
	LinkLuaModifier( "modifier_double_fee" , "scripts/vscripts/modifiers/modifier_double_fee.lua" , LUA_MODIFIER_MOTION_NONE )
	
	GameRules.giveups = {}
	GameRules.custom_state = STATE_NONE

	GameRules.positions = {1,1,1,1,1,1,1,1}  --玩家当前的位置
	print("gameControl inited")
end
--[[
	@開始新的回合
	@色子重新投擲
	@改变当前操作的玩家
	@UI通知客户端开始计时
	@给其他玩家施加石化的modifier
	@解封当前玩家的石化modifier
]]
function M.startRound( i )
	M.doAction()
	local id = GameRules.playerlist[i]
	GameRules.curPlayer = id
	for nPlayerID = 0, ( DOTA_MAX_TEAM_PLAYERS - 1 ) do
		local hPlayer = PlayerResource:GetPlayer( nPlayerID )
		if hPlayer then
			local hero = hPlayer:GetAssignedHero()
			--防止石化状态下按下的命令会自动排队执行
			hero:Stop()
			if nPlayerID == id then
				M.checkHeroState(hero,true)
				hero.actionDone = false
				hero:RemoveModifierByName("modifier_medusa_stone_gaze_stone")
			else
				M.checkHeroState(hero,false)
				hero:AddNewModifier(hero, nil,"modifier_medusa_stone_gaze_stone",{})
			end
		end
	end

	CustomGameEventManager:Send_ServerToAllClients("S2C_CHANGE_PLAYER",{playerid=id})
	
	M.resumeRound(id)
end
--[[
	继续之前中断的回合，比如拍卖
	@id 当前玩家id
	@startTime 计时器的起点
]]
function M.resumeRound( id,startTime )
	
	if not GameRules.last_round_timer and startTime then
		--不存在这个参数表明当前的拍卖模式是玩家自动操作进入的，不重新倒计时，直接endRound
		doAutoAction(id)
		return
	end
	local name = PlayerResource:GetPlayerName(id)
	GameRules.round_timer = createTimerBar(sec_round,function(  )
		M._doAutoAction(id)
	end,"#TIP_NEXT_ROUND",{name},startTime)
end

function M.doAutoAction(hero,id)
	if not hero.actionDone then
		M.doDice(hero,true)
	else
		M.endRound(id)
	end
end

function M._doAutoAction( id )
	local hero = PlayerResource:GetSelectedHeroEntity(id)
	if hero.isMoving then
		print("hero.isMoving = true")
		--当前已经在行动 不要在中间插入行动
		Timers:CreateTimer(1,function(  )
			M._doAutoAction(id)
		end)
	else
		M.doAction()
		--倒计时结束之后自动操作
		M.doAutoAction(hero,id)
	end
end

--[[
	@回合結束
	@好像没啥要做的操作
	@如果时间结束的时候玩家处于掉线状态，自动行动
]]
function M.endRound( id )
	local hero = PlayerResource:GetSelectedHeroEntity(id)

	if hero.doubledice > 0 then
		local idx = getIndexById(id)
		M.startRound(idx)
		return
	end
	local idx = getIndexById(id)
	local newid = getNextIdx(idx)
	--[[
		如果当前是在拍卖状态或者交易状态，都强制取消
	]]
	if GameRules.custom_state == STATE_PAIMAI then
		--放弃拍卖
		roleData.canclePM()
	elseif GameRules.custom_state == STATE_TRADING then
		roleData.cancleTrade(id,true)
	elseif GameRules.custom_state == STATE_CHOICE then
		roleData.doChoice(id,GameRules.positions[id+1],1)
	end

	local function cbSuccess(  )
		M.newRound( newid,hero )
	end

	local function cbFail( )
		M.giveupGame(id)
	end
	local function cbSell() 
		M.autoDiya(id,cbSuccess,cbFail)
	end
	local curgold = _getGold(id)
	if curgold < 0 then
		M.autoSell(id,cbSell)
	else
		cbSuccess()
	end
end

function M.giveupGame( id )
	local hero = PlayerResource:GetSelectedHeroEntity(id)
	hero:ForceKill(false)
	local idx = getIndexById(id)
	GameRules.giveups[idx] = true
	table.remove(GameRules.playerlist,idx)

	roleData.removeCitiesById(id)

	CustomGameEventManager:Send_ServerToAllClients("S2C_PLAYER_FAIL",{id=id})
	--检测游戏是否结束
	if #GameRules.playerlist < 1 then
		M.stopGameControl(  )
	end
end

function M.newRound( newid,hero )
	M.startRound(newid)
	EmitGlobalSound("ui.npe_objective_complete")
	local oneRoundAb = {
		"hero_travel",
		"hero_aoyunhui",
		"hero_tonado",
	}
	for i,v in ipairs(oneRoundAb) do
		if hero:HasAbility(v) then
			hero:RemoveAbility(v)
		end
	end
end

function M.stopGameControl(  )

	M.doAction()

	local infos = {}
	for i=0,MAX_PLAYER do
		if PlayerResource:IsValidPlayerID(i) then
			local t = {
				name = PlayerResource:GetPlayerName(i),
				gold = _getGold(i),
			}
			table.insert(infos,t)
		end
	end
	table.sort(infos,function ( a,b )
		return a.gold > b.gold
	end)
	
	CustomGameEventManager:Send_ServerToAllClients("S2C_FINISH_GAME",infos)
	Timers:CreateTimer(2,function (  )
		GameRules:SetGameWinner(DOTA_TEAM_GOODGUYS)
	end)
end

function M.autoSell( id ,callback)
	
	local cities = roleData.getCitiesCanLvdown(id)
	if cities[1] then
		roleData.delvCityById(id,cities[1])
		local gold = _getGold(id)
		if gold < 0 then
			Timers:CreateTimer(1.5,function(  )
				M.autoSell(id,callback)
			end)
		else
			return true
		end
	else
		--能跑到这里说明找不到可以让gold>0的方法
		callback()
		return false
	end
end
--先出售再抵押  优先级是这样的
--递归改循环  不好改，因为这些动作发生有延迟
function M.autoDiya( id,success,fail )
	local cities = roleData.getCitiesCanDiya(id)
	if cities[1] then
		roleData.diyaCityById(id,cities[1])
		local gold = _getGold(id)
		if gold < 0 then
			Timers:CreateTimer(1.5,function(  )
				M.autoDiya(id,success,fail)
			end)
		else
			success()
			return true
		end
	else
		fail()
		return false
	end
end

function M.checkHeroState( hero,inTurn )

end

function M.removeAllAbility( hero )
	local abc = hero:GetAbilityCount()
	for i=0,abc-1 do
		local ab = hero:GetAbilityByIndex(i)
		if ab then
			hero:RemoveAbility(ab:GetAbilityName())
		end
	end
end

function M.doAction(  )
	unregisterTimer(GameRules.round_timer)
	GameRules.round_timer = nil
end

function forwardPoint(hero,num)
	local id = hero:GetPlayerOwnerID()
	local curPoint = GameRules.positions[id+1]
	local newPoint = to + curPoint
	local firstPoint = math.min(40,newPoint)

	M.passingPoint(hero,firstPoint)

	if newPoint > 40 then
		M.passingPoint(hero,1)
		curPoint = 1
		newPoint = newPoint - 40 + 1
		for i=curPoint,newPoint do
			M.passingPoint(hero,i)
		end
	end
	GameRules.curTarget = newPoint
end

function M.passingPoint( hero,point )
	local id = hero:GetPlayerOwnerID()
	GameRules.positions[id+1] = point
	local info = GameRules.blocks[point]

	--执行命令放在最后面
	local orderTable = {
		UnitIndex = hero:entindex(),
		OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
		Position = info.unit:GetAbsOrigin(),
		queue = 1,
	}
	ExecuteOrderFromTable(orderTable)
	
end

function M.doPass( hero,point )
	local id = hero:GetPlayerOwnerID()
	local info = GameRules.blocks[point]
	if not info then
		return
	end

	if info.pass then
		info.pass(hero)
	end
end
--计算经过的点需要付出的金币
local function calcPassGold( idx )
	 local city = roleData.getCityById(idx)
	-- if not city.gold then
	-- 	return 0
	-- end
	-- local cost = city.gold
	-- if city.build then
	-- 	cost = cost + city.build[city.lv] or 0
	-- end
	-- return cost
	return city.build[city.lv] or 0
end

--在触发该点的事件之后触发回调
function M.cbPointEffect( id )
	local hero = PlayerResource:GetSelectedHeroEntity(id)
	local curPoint = GameRules.positions[id+1]
	local data = roleData.getCityById(curPoint)
	if data.owner then
		if data.owner ~= id and data.build then
			--要付钱
			local cost = calcPassGold(curPoint)
			if data.diya ~= true then
				if hero:HasModifier("modifier_invulnerable") then
					cost = 0
					hero:RemoveModifierByName("modifier_invulnerable")
				end
				local target = PlayerResource:GetSelectedHeroEntity(data.owner)
				if target.isInJy or hero.isInJy then
					cost = 0
				end
				ModifyGoldLtx(id,-cost,data.owner)
			end
			--M.endRound(id)
		end
	else
		--无主之地
		-- hero:AddAbility("hero_buy")
		-- hero:AddAbility("hero_paimai")
		if data.build or data.gold then
			GameRules.custom_state = STATE_CHOICE
			CustomGameEventManager:Send_ServerToAllClients("S2C_SHOW_CHOICE",{citydata=roleData.getCityById(curPoint),curid=id,lvdesc=LVDESC[data.suit]})
		end
	end
	
	--hero:AddAbility("hero_end")
	M.LevelUpAllAbility(hero)
	if data.paimai then
		--拥有这个字段的只能拍卖，否则既能买也能竞价
		roleData.startPM(id,curPoint)
	end
	--运行到下面说明是自动操作的，拍卖会自动放弃
	if not GameRules.round_timer then
		M.endRound(id)
	end
end

function M.LevelUpAllAbility( hero )
	local abc = hero:GetAbilityCount()
	for i=0,abc-1 do
		local ab = hero:GetAbilityByIndex(i)
		if ab then
			ab:SetLevel(1)
		end
	end 
end

function M.doPoint( hero,point )
	print("M.doPoint")
	local id = hero:GetPlayerOwnerID()
	local info = GameRules.blocks[point]
	if not info then
		return
	end
	-- if info.pass then
	-- 	info.pass(hero)
	-- end
	if info.func then
		info.func(hero,M.cbPointEffect)
	else
		M.cbPointEffect(id)
		--没有特定的回调函数
		--普通地皮，只能购买或者竞价
		
	end
	-- if GameRules.curTarget == GameRules.positions[id+1] then
	-- 	if GameRules.blocks[GameRules.curTarget].func then
	-- 		GameRules.blocks[GameRules.curTarget].func(hero)
	-- 	end
	-- end
	
	-- if info.owner ~= hero then
	-- 	--TODO 计算需要支付的金钱
	-- 	local gold = info.curBuild.gold
	-- end

	--如果有奥运会标志，扣额外的钱
	local city = roleData.getCityById(point)
	if city.aoyunhui then
		for i,ayh in ipairs(city.aoyunhui) do
			local owner = city.owner
			ModifyGoldLtx(id,-500,owner)
			ayh.unit:ForceKill(false)
		end
		city.aoyunhui = {}
	end
	hero.isMoving = false
end
--获取下一回合的玩家
function M.getNextId( idx )
	local record = {}

	while true do
		idx = idx + 1
		if idx > #GameRules.playerlist then
			idx = 1
		end
		if record[idx] then
			return false
		else
			record[idx] = true
		end
		return GameRules.playerlist[idx]
	end
end

function M.checkDoDice( hero,isAuto )
	if hero.isMoving then
		--当前已经在行动 不要在中间插入行动
		Timers:CreateTimer(1,function(  )
			M.checkDoDice(hero,isAuto)
		end)
		return
	else
		M.truelyDoDice(hero,isAuto)
		return 
	end
end

function M.truelyDoDice( hero,isAuto )
	hero.isMoving = true
	hero.actionDone = true
	local rd1 = RandomInt(1,6)
	local rd2 = RandomInt(1,6)
	if rd1 == rd2 then
		hero.actionDone = false
	end
	-- for i=1,rd1 do
	-- 	local unit = GameRules.dices[i]
	-- --	unit:SetAbsOrigin(unit:GetAbsOrigin()+Vector(500,500,2000))
	-- 	unit:RemoveNoDraw()
	-- 	--unit:AddNewModifier(unit, nil, "modifier_dice_motion", {duration = 0.5})
	-- end
	-- for i=1,rd2 do
	-- 	local unit = GameRules.dices[i+6]
	-- --	unit:SetAbsOrigin(unit:GetAbsOrigin()+Vector(500,500,2000))
	-- 	unit:RemoveNoDraw()
	-- 	--unit:AddNewModifier(unit, nil, "modifier_dice_motion", {duration = 0.5})
	-- end
	for i=0,MAX_PLAYER do
		if PlayerResource:IsValidPlayerID(i) then
			PlayerResource:SetCameraTarget(i, GameRules.center_unit)
		end
	end
	local selfGameMode = GameRules:GetGameModeEntity()
	selfGameMode:SetContextThink(DoUniqueString("cameraMove"),
	function (  ) 
		for i=0,MAX_PLAYER do
			if PlayerResource:IsValidPlayerID(i) then
				PlayerResource:SetCameraTarget(i, nil) 
			end
		end
	end,2)

	local rds = {
		rd1,rd2
	}
	local pos = {
		Vector(-100,0,150),
		Vector(100,0,150),
	}
	for i=1,2 do
		local pts = ParticleManager:CreateParticle("particles/rotation.vpcf",PATTACH_WORLDORIGIN,hero)
		ParticleManager:SetParticleControl(pts, 3, pos[i])
		local tornado = ParticleManager:CreateParticle("particles/units/heroes/hero_invoker/invoker_tornado_funnel.vpcf",PATTACH_WORLDORIGIN,hero)
		ParticleManager:SetParticleControl(tornado, 3, pos[i])
		local dicPts = "dice"..i
		if GameRules[dicPts] then
			ParticleManager:DestroyParticle(GameRules[dicPts],false)
		end
		Timers:CreateTimer(2.5,function (  )
			ParticleManager:DestroyParticle(pts,false)
			ParticleManager:DestroyParticle(tornado,false)
			local partName = string.format("particles/dice_%d.vpcf",rds[i])
			GameRules[dicPts] = ParticleManager:CreateParticle(partName,PATTACH_WORLDORIGIN,hero)
			ParticleManager:SetParticleControl(GameRules[dicPts], 3,pos[i])

		end)
	end
	
	local name = hero:GetUnitName()
	M.addEvent(function()
		local id = hero:GetPlayerOwnerID()
		local curPoint = GameRules.positions[id+1]
		if rd1 == rd2 then
			hero.doubledice = hero.doubledice + 1
			--如果在坐牢，放出来
			hero.isInJy = false
		else
			hero.doubledice = 0
			if hero.isInJy then
				hero.isMoving = false
				if isAuto then
					M.endRound(id)
				end
			end
		end
		
		if hero.doubledice == 3 then
			hero.doubledice = 0
			block.Zuolao(hero)
			local idx = getIndexById(id)
			M.startRound(getNextIdx(idx))
		else
			if not hero.isInJy then
				local toPoint = getNextBlock(curPoint,rd1+rd2)
				M[name](hero,toPoint)
			end
		end
	end,3)

	
end

function M.doDice( hero,isAuto )
	M.checkDoDice(hero,isAuto)
	
end


function M.doThink( top )
	if top.func then
		if toRemove[top.bz] then
			toRemove[top.bz] = nil
		else
			local ret = top.func()
			if ret and ret >= 0 then
				top.expire = top.expire + ret
				return true
			end
		end
	end
	return false
end

function M.onGameThink(  )
	local cur_time = GameRules:GetGameTime()
	while true do
		local top = custom_timer:top()
		if top and top.expire <= cur_time then
			local ret = M.doThink(top)
			if ret then
				custom_timer:pushDown()
			else
				custom_timer:pop()
			end
		else
			break
		end
	end
	return 0.01
end

function M.addEvent( func,delay )
	local t = {}
	t.func = func
	t.expire = GameRules:GetGameTime() + delay
	t.bz = DoUniqueString("eventflag")
	custom_timer:push(t)
	return t.bz
end
--[[
	@重连游戏好像没什么要处理的
	@
]]
function M.reconnect(  )
	-- body
end

function M.removeEvent( bz )
	toRemove[bz] = true
end

function M.onThink(  )
	
end
local function _getNextPoint( curPoint,isAnti )
	if isAnti then
		curPoint = curPoint - 1
		if curPoint < 1 then
			curPoint = MAX_LEN
		end
	else
		curPoint = curPoint + 1
		if curPoint > MAX_LEN then
			curPoint = 1
		end
	end
	return curPoint
end
--需要考虑顺时针和逆时针
local function getPointSplit( curPoint,toPoint,isAnti )
	local guaijiao = block.getGuaijiao()
	local ret = {}

	while true do
		curPoint = _getNextPoint(curPoint,isAnti)
		for i,v in ipairs(guaijiao) do
			if curPoint == v then
				table.insert(ret,curPoint)
			end
		end
		if curPoint == toPoint then
			break
		end
	end
	return ret
end

local function llanmao(caster,curPoint,toPoint,isAnti)
	local casterLoc = caster:GetAbsOrigin()
	local id = caster:GetPlayerOwnerID()
	local speed = 1500
	local destroy_radius = 100
	local vision_radius = 1000
	local radius = 125
	local guaijiao = getPointSplit(curPoint,toPoint,isAnti)
	--dump(guaijiao,"guaijiao = ")
	local target,newPoint
	local gjnum = #guaijiao
	if gjnum ~= 0 then
		newPoint = guaijiao[1]
	else
		newPoint = toPoint
	end
	-- print("curPoint = ",curPoint)
	-- print("newPoint = ",newPoint)
	-- dump(guaijiao,"guaijiao = ")
	target = GameRules.blocks[newPoint].unit:GetAbsOrigin()
	-- Variables based on modifiers and precaches
	local particle_dummy = "particles/status_fx/status_effect_base.vpcf"
	local loop_sound_name = "Hero_StormSpirit.BallLightning.Loop"
	local modifierName = "modifier_ball_lightning"
	
	-- Necessary pre-calculated variable
	local currentPos = casterLoc
	local intervals_per_second = speed / destroy_radius		-- This will calculate how many times in one second unit should move based on destroy tree radius
	local forwardVec = ( target - casterLoc ):Normalized()
	caster:SetForwardVector(forwardVec)
	-- Start
	local distance = 0.0

	-- Create dummy projectile
	local projectileTable =
	{
		EffectName = particle_dummy,
		Ability = nil,
		vSpawnOrigin = caster:GetAbsOrigin(),
		vVelocity = speed * forwardVec,
		fDistance = 99999,
		fStartRadius = radius,
		fEndRadius = radius,
		Source = caster,
		bHasFrontalCone = false,
		bReplaceExisting = true,
		bProvidesVision = true,
		iUnitTargetTeam = DOTA_UNIT_TARGET_TEAM_ENEMY,
		iUnitTargetFlags = DOTA_UNIT_TARGET_FLAG_MAGIC_IMMUNE_ENEMIES,
		iUnitTargetType = DOTA_UNIT_TARGET_HERO + DOTA_UNIT_TARGET_BASIC,
		iVisionRadius = vision_radius,
		iVisionTeamNumber = caster:GetTeamNumber()
	}
	local projectileID = ProjectileManager:CreateLinearProjectile( projectileTable )
	caster:AddNewModifier(caster, nil, modifierName, {})
	Timers:CreateTimer( function()
			-- Spending mana
			distance = distance + speed / intervals_per_second
		
			-- Update location
			currentPos = currentPos + forwardVec * ( speed / intervals_per_second )
			-- caster:SetAbsOrigin( currentPos ) -- This doesn't work because unit will not stick to the ground but rather travel in linear
			FindClearSpaceForUnit( caster, currentPos, false )
			
			-- Check if unit is close to the destination point
			if ( target - currentPos ):Length2D() <= speed / intervals_per_second then
				-- Exit condition
				caster:RemoveModifierByName( modifierName )
				
				
				GameRules.positions[id+1] = newPoint
				M._doPass(caster,curPoint,newPoint,isAnti)
				curPoint = newPoint
				if newPoint == toPoint then
					M.doPoint(caster,newPoint)
					caster.isMoving = false
					StopSoundEvent( loop_sound_name, caster )
					--位移有偏差，手动修正
					local pos = GameRules.blocks[newPoint].unit:GetAbsOrigin()
					local gpos = GetGroundPosition(pos,caster)
					caster:SetAbsOrigin(gpos)
				end
				if gjnum ~= 0 and guaijiao[1] ~= toPoint then
					llanmao(caster,newPoint,toPoint,isAnti)
				end
				return nil
			else
				return 1 / intervals_per_second
			end
		end
	)
end

function M.npc_dota_hero_storm_spirit( caster,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	caster.isMoving = true
	local curPoint = GameRules.positions[id+1]
	EmitSoundOn("Hero_StormSpirit.BallLightning", caster)
	EmitSoundOn("Hero_StormSpirit.BallLightning.Loop", caster)
	llanmao(caster,curPoint,toPoint,isAnti)
end

function bbainiu( caster,curPoint,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	local guaijiao = getPointSplit(curPoint,toPoint,isAnti)
	local target,newPoint
	local gjnum = #guaijiao
	if gjnum ~= 0 then
		newPoint = guaijiao[1]
	else
		newPoint = toPoint
	end
	target = GameRules.blocks[newPoint].unit:GetAbsOrigin()
	local face = (target - caster:GetAbsOrigin() ):Normalized()
	caster:SetForwardVector(face)
	Timers:CreateTimer( 
		function()
			-- Check if unit is close to the destination point

			local currentPos = caster:GetAbsOrigin()
			local forwardVec = (target - currentPos ):Normalized()
			
			if ( target - currentPos ):Length2D() <= 50 then
				
				
				GameRules.positions[id+1] = newPoint
				M._doPass(caster,curPoint,newPoint,isAnti)
				curPoint = newPoint
				if newPoint == toPoint then
					caster.isMoving = false
					M.doPoint(caster,newPoint)
					
					ParticleManager:DestroyParticle(caster.movePart,false)
					--位移有偏差，手动修正
					local pos = GameRules.blocks[newPoint].unit:GetAbsOrigin()
					local gpos = GetGroundPosition(pos,caster)
					caster:SetAbsOrigin(gpos)
					caster:RemoveModifierByName("modifier_bainiu_motion")
				end
				if gjnum ~= 0 and guaijiao[1] ~= toPoint then
					bbainiu(caster,newPoint,toPoint,isAnti)
				end
				return nil
			else
				local pos = currentPos+forwardVec*1500/50
				local gpos = GetGroundPosition(pos,caster)
				caster:SetAbsOrigin(gpos)
				return 0.01
			end
		end
	)
end

function M._doPass( caster,curPoint,newPoint,isAnti )
	while true do
		curPoint = _getNextPoint(curPoint,isAnti)
		M.doPass(caster,curPoint)
		if curPoint == newPoint then
			break
		end
	end
end

function M.npc_dota_hero_spirit_breaker( caster,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	caster.isMoving = true
	local curPoint = GameRules.positions[id+1]
	EmitSoundOn("Hero_Spirit_Breaker.ChargeOfDarkness", caster)
	caster.movePart = ParticleManager:CreateParticle("particles/econ/items/spirit_breaker/spirit_breaker_iron_surge/spirit_breaker_charge_iron.vpcf",PATTACH_ABSORIGIN_FOLLOW,caster)
	ParticleManager:SetParticleControl(caster.movePart,0,caster:GetAbsOrigin())
	caster:AddNewModifier(caster, nil,"modifier_bainiu_motion",{})
	bbainiu(caster,curPoint,toPoint,isAnti)
end

function kkunkka( caster,curPoint,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	local guaijiao = getPointSplit(curPoint,toPoint,isAnti)
	local target,newPoint
	local gjnum = #guaijiao
	if gjnum ~= 0 then
		newPoint = guaijiao[1]
	else
		newPoint = toPoint
	end
	local curPos = caster:GetAbsOrigin()
	ParticleManager:SetParticleControl(caster.movePart,3,curPos)
	target = GameRules.blocks[newPoint].unit:GetAbsOrigin()
	local face = (target - curPos ):Normalized()
	caster:SetForwardVector(face)
	Timers:CreateTimer( 
		function()
			-- Check if unit is close to the destination point

			local currentPos = caster:GetAbsOrigin()
			local forwardVec = (target - currentPos ):Normalized()

			if ( target - currentPos ):Length2D() <= 50 then
				
				
				GameRules.positions[id+1] = newPoint
				M._doPass(caster,curPoint,newPoint,isAnti)
				curPoint = newPoint
				if newPoint == toPoint then
					caster.isMoving = false
					M.doPoint(caster,newPoint)
					
					ParticleManager:DestroyParticle(caster.movePart,false)
					--位移有偏差，手动修正
					local pos = GameRules.blocks[newPoint].unit:GetAbsOrigin()
					local gpos = GetGroundPosition(pos,caster)
					caster:SetAbsOrigin(gpos)
					caster:RemoveModifierByName("modifier_kunkka_motion")
				end
				if gjnum ~= 0 and guaijiao[1] ~= toPoint then
					kkunkka(caster,newPoint,toPoint,isAnti)
				end
				return nil
			else
				local pos = currentPos+forwardVec*1500/50
				local gpos = GetGroundPosition(pos,caster)
				caster:SetAbsOrigin(gpos)
				ParticleManager:SetParticleControl(caster.movePart,3,currentPos+forwardVec*1500/25)
				return 0.01
			end
		end
	)
end

function M.npc_dota_hero_kunkka( caster,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	caster.isMoving = true
	local curPoint = GameRules.positions[id+1]
	EmitSoundOn("Ability.Ghostship", caster)
	caster.movePart = ParticleManager:CreateParticle("particles/kunkka_movepart.vpcf",PATTACH_ABSORIGIN_FOLLOW,caster)
	ParticleManager:SetParticleControl(caster.movePart,3,caster:GetAbsOrigin())
	caster:AddNewModifier(caster, nil,"modifier_kunkka_motion",{})
	kkunkka(caster,curPoint,toPoint,isAnti)
end

function mmorphling( caster,curPoint,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	local guaijiao = getPointSplit(curPoint,toPoint,isAnti)
	local target,newPoint
	local gjnum = #guaijiao
	if gjnum ~= 0 then
		newPoint = guaijiao[1]
	else
		newPoint = toPoint
	end
	local curPos = caster:GetAbsOrigin()
	ParticleManager:SetParticleControl(caster.movePart,3,curPos)
	target = GameRules.blocks[newPoint].unit:GetAbsOrigin()
	local face = (target - curPos ):Normalized()
	caster:SetForwardVector(face)
	Timers:CreateTimer( 
		function()
			-- Check if unit is close to the destination point

			local currentPos = caster:GetAbsOrigin()
			local forwardVec = (target - currentPos ):Normalized()

			if ( target - currentPos ):Length2D() <= 50 then
				
				
				GameRules.positions[id+1] = newPoint
				M._doPass(caster,curPoint,newPoint,isAnti)
				curPoint = newPoint
				if newPoint == toPoint then
					caster.isMoving = false
					M.doPoint(caster,newPoint)
					
					ParticleManager:DestroyParticle(caster.movePart,false)
					--位移有偏差，手动修正
					local pos = GameRules.blocks[newPoint].unit:GetAbsOrigin()
					local gpos = GetGroundPosition(pos,caster)
					caster:SetAbsOrigin(gpos)
					caster:RemoveModifierByName("modifier_morphling_motion")
				end
				if gjnum ~= 0 and guaijiao[1] ~= toPoint then
					mmorphling(caster,newPoint,toPoint,isAnti)
				end
				return nil
			else
				local pos = currentPos+forwardVec*1500/50
				local gpos = GetGroundPosition(pos,caster)
				caster:SetAbsOrigin(gpos)
				ParticleManager:SetParticleControl(caster.movePart,3,currentPos+forwardVec*1500/50)
				return 0.01
			end
		end
	)
end

function M.npc_dota_hero_morphling( caster,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	caster.isMoving = true
	local curPoint = GameRules.positions[id+1]
	EmitSoundOn("Hero_Morphling.Waveform", caster)
	caster.movePart = ParticleManager:CreateParticle("particles/econ/items/morphling/morphling_crown_of_tears/morphling_crown_waveform_splash_f.vpcf",PATTACH_ABSORIGIN_FOLLOW,caster)
	ParticleManager:SetParticleControl(caster.movePart,3,caster:GetAbsOrigin())
	caster:AddNewModifier(caster, nil,"modifier_morphling_motion",{})
	mmorphling(caster,curPoint,toPoint,isAnti)
end

function mmagnataur( caster,curPoint,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	local guaijiao = getPointSplit(curPoint,toPoint,isAnti)
	local target,newPoint
	local gjnum = #guaijiao
	if gjnum ~= 0 then
		newPoint = guaijiao[1]
	else
		newPoint = toPoint
	end
	local curPos = caster:GetAbsOrigin()
	target = GameRules.blocks[newPoint].unit:GetAbsOrigin()
	local face = (target - curPos ):Normalized()
	caster:SetForwardVector(face)
	Timers:CreateTimer( 
		function()
			-- Check if unit is close to the destination point

			local currentPos = caster:GetAbsOrigin()
			local forwardVec = (target - currentPos ):Normalized()

			if ( target - currentPos ):Length2D() <= 50 then
				
				
				GameRules.positions[id+1] = newPoint
				M._doPass(caster,curPoint,newPoint,isAnti)
				curPoint = newPoint
				if newPoint == toPoint then
					caster.isMoving = false
					M.doPoint(caster,newPoint)
					
					--位移有偏差，手动修正
					local pos = GameRules.blocks[newPoint].unit:GetAbsOrigin()
					local gpos = GetGroundPosition(pos,caster)
					caster:SetAbsOrigin(gpos)
					caster:RemoveModifierByName("modifier_magnataur_motion")
				end
				if gjnum ~= 0 and guaijiao[1] ~= toPoint then
					mmagnataur(caster,newPoint,toPoint,isAnti)
				end
				return nil
			else
				local pos = currentPos+forwardVec*1500/50
				local gpos = GetGroundPosition(pos,caster)
				caster:SetAbsOrigin(gpos)
				return 0.01
			end
		end
	)
end

function M.npc_dota_hero_magnataur( caster,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	caster.isMoving = true
	local curPoint = GameRules.positions[id+1]
	EmitSoundOn("Hero_Magnataur.Skewer.Cast", caster)
	caster:AddNewModifier(caster, nil,"modifier_magnataur_motion",{})
	mmagnataur(caster,curPoint,toPoint,isAnti)
end

function rrubick( caster,curPoint,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	local guaijiao = getPointSplit(curPoint,toPoint,isAnti)
	local target,newPoint
	local gjnum = #guaijiao
	if gjnum ~= 0 then
		newPoint = guaijiao[1]
	else
		newPoint = toPoint
	end
	local curPos = caster:GetAbsOrigin()
	target = GameRules.blocks[newPoint].unit:GetAbsOrigin()
	local face = (target - curPos ):Normalized()
	caster:SetForwardVector(face)
	Timers:CreateTimer( 
		function()
			-- Check if unit is close to the destination point

			local currentPos = caster:GetAbsOrigin()
			local forwardVec = (target - currentPos ):Normalized()

			if ( target - currentPos ):Length2D() <= 50 then
				
				
				GameRules.positions[id+1] = newPoint
				M._doPass(caster,curPoint,newPoint,isAnti)
				curPoint = newPoint
				if newPoint == toPoint then
					caster.isMoving = false
					M.doPoint(caster,newPoint)
					
					--位移有偏差，手动修正
					local pos = GameRules.blocks[newPoint].unit:GetAbsOrigin()
					local gpos = GetGroundPosition(pos,caster)
					caster:SetAbsOrigin(gpos)
					caster:RemoveModifierByName("modifier_rubick_motion")
				end
				if gjnum ~= 0 and guaijiao[1] ~= toPoint then
					rrubick(caster,newPoint,toPoint,isAnti)
				end
				return nil
			else
				local pos = currentPos+forwardVec*1500/50
				local gpos = GetGroundPosition(pos,caster)
				caster:SetAbsOrigin(gpos)
				return 0.01
			end
		end
	)
end

function M.npc_dota_hero_rubick( caster,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	caster.isMoving = true
	local curPoint = GameRules.positions[id+1]
	EmitSoundOn("Hero_Rubick.Taunt_Giddy_Up", caster)
	caster:AddNewModifier(caster, nil,"modifier_rubick_motion",{})
	rrubick(caster,curPoint,toPoint,isAnti)
end

function zzuolao( caster,curPoint,toPoint,isAnti )
	local id = caster:GetPlayerOwnerID()
	local guaijiao = getPointSplit(curPoint,toPoint,isAnti)
	local target,newPoint
	local gjnum = #guaijiao
	if gjnum ~= 0 then
		newPoint = guaijiao[1]
	else
		newPoint = toPoint
	end
	local curPos = caster:GetAbsOrigin()
	target = GameRules.blocks[newPoint].unit:GetAbsOrigin()
	local face = (target - curPos ):Normalized()
	caster:SetForwardVector(face)
	Timers:CreateTimer( 
		function()
			-- Check if unit is close to the destination point

			local currentPos = caster:GetAbsOrigin()
			local forwardVec = (target - currentPos ):Normalized()

			if ( target - currentPos ):Length2D() <= 50 then
				
				
				GameRules.positions[id+1] = newPoint
				curPoint = newPoint
				if newPoint == toPoint then
					caster.isMoving = false
					M.doPoint(caster,newPoint)
					--位移有偏差，手动修正
					local pos = GameRules.blocks[newPoint].unit:GetAbsOrigin()
					local gpos = GetGroundPosition(pos,caster)
					caster:SetAbsOrigin(gpos)
					caster:RemoveModifierByName("modifier_ruyu_motion")
				end
				if gjnum ~= 0 and guaijiao[1] ~= toPoint then
					zzuolao(caster,newPoint,toPoint,isAnti)
				end
				return nil
			else
				local pos = currentPos+forwardVec*3500/50
				local gpos = GetGroundPosition(pos,caster)
				caster:SetAbsOrigin(gpos)
				return 0.01
			end
		end
	)
end

function M.Zuolao( caster,toPoint,isAnti )
	caster.isMoving = true
	local id = caster:GetPlayerOwnerID()
	local curPoint = GameRules.positions[id+1]
	EmitSoundOn("Hero_Batrider.FlamingLasso.Cast", caster)
	local pos = GameRules.blocks[toPoint].unit:GetAbsOrigin()
	caster:AddNewModifier(caster, nil,"modifier_ruyu_motion",{posx = pos.x,posy = pos.y,posz = pos.z})
	zzuolao(caster,curPoint,toPoint,isAnti)
end

M.init()

return M